package com.sanss.trapParse.trap;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.SQLException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.snmp4j.PDU;

import com.sanss.trapParse.db.*;
import com.sanss.trapParse.info.*;
import com.sanss.trapParse.method.*;

public class TrapHandler {

	private String oid;
	private TrapInfo ti;
	private String mibCfgFile;
	private String ruleDir;
	private String location;
	private Logger logger;
	private String[] trapContent;

	private DBServ db;
	private StatusInfo si;

	private final String regExp = "[a-f0-9]{2}:{1}";

	public TrapHandler(TrapInfo ti, String mibCfgFile, String ruleDir,
			String location, Logger logger) {
		this.ti = ti;
		this.mibCfgFile = mibCfgFile;
		this.ruleDir = ruleDir;
		this.location = location;
		this.logger = logger;
		db = new DBServ();
		si = new StatusInfo();
	}

	private void dbConn() throws SQLException {
		db.Init();
		db.Connect();
	}

	private boolean formatTrap() throws Exception {
		// get rules
		ArrayList<TrapPDU> tpdu = ti.getTrapPDU();
		tpdu.remove(0);
		oid = tpdu.get(0).getVariable().toString();
		tpdu.remove(0);
		FileInputStream fis = ruleLocator();
		if (fis == null)// no rule found
			return false;
		BufferedReader br = new BufferedReader(new InputStreamReader(fis));

		trapContent = new String[tpdu.size()];
		for (int i = 0; i < tpdu.size(); i++) {
			String value = tpdu.get(i).getVariable();
			Pattern pattern = Pattern.compile(regExp);
			Matcher match = pattern.matcher(value);
			if (match.find() && value.charAt(2) == ':'
					&& value.charAt(5) == ':')
				// octect string contains Chinese character
				trapContent[i] = getChinese(value);
			else
				trapContent[i] = value;
		}

		String line = null;// line reader

		// get node
		// String node = db.NodeQuery(ti.getSender());

		// get timestamp
		si.setTimestamp(ti.getTimestamp());
		// get manager
		int managerID = Integer.parseInt(oid.split("\\.")[6]);
		// String manager = db.ManagerQuery(managerID);
		// si.setManager(manager);
		si.setManager("NA");
		// get alertKey
		StringBuilder alertKey = new StringBuilder("");
		for (String s : trapContent) {
			alertKey.append(s).append(",");
		}
		si.setAlertKey(alertKey.substring(0, alertKey.length() - 1));

		String summary = null;
		// for identifier
		String OS_Severity = null;
		String DEFAULT_Severity = null;
		String DEFAULT_Type = null;
		String DEFAULT_ExpireTime = null;
		String agent = null;
		String alertGroup = null;

		while ((line = br.readLine()) != null) {
			line = line.trim();
			if (line.equals(""))
				continue;
			if (line.indexOf(oid) != -1) {
				while (!(line = br.readLine()).equals("}")) {
					// empty line
					if (line.equals(""))
						continue;

					// comment
					if (line.startsWith("#"))
						continue;

					// check keywords to call dynamic method first
					if (line.indexOf("__") > 0) {
						String setterName = line.split("\\=")[0].trim();

						// check if one field needs several method to determine
						if (line.indexOf("-or-") > 0) {
							String[] scripts = line.split("\\=")[1].trim()
									.split("\\-or\\-");
							String[] result = new String[scripts.length];
							for (int i = 0; i < scripts.length; i++) {
								result[i] = invokeMethod(scripts[i]);
								if (result[i] != null && !result[i].equals("")) {
									if (setterName.indexOf("@") == 0)
										dynamicStringValueSetter(
												setterName.substring(1),
												result[i]);
								}
							}
							continue;
						} else {
							String script = line.split("\\=")[1].trim();
							String result = invokeMethod(script);
							if (result != null && !result.equals("")) {
								if (setterName.indexOf("@") == 0)
									dynamicStringValueSetter(
											setterName.substring(1), result);
							}
							continue;
						}
					}

					// get severity
					if (line.indexOf("@severity = ") >= 0) {
						String severityIn = line;
						setSeverity(severityIn, trapContent);
						continue;
					}

					// get OS_EventId
					if (line.indexOf("$OS_EventId = ") >= 0) {
						String OS_EventId = line.split("\\=")[1].trim();
						si.setOS_EventId(OS_EventId);
						continue;
					}

					// get agent
					if (line.indexOf("@Agent = ") >= 0) {
						agent = line.split("\\=")[1].trim();
						si.setAgent(agent);
						continue;
					}
					// get alertgroup
					if (line.indexOf("@AlertGroup = ") >= 0) {
						alertGroup = line.split("\\=")[1].trim();
						si.setAlertGroup(alertGroup);
						continue;
					}

					// get summary
					if (line.indexOf("@Summary = ") >= 0) {
						summary = line.split("\\=")[1].trim();
						if (summary.indexOf("@") == -1)
							si.setSummary(setSummary(summary, trapContent));
						else {
							String sumFile = ruleDir + "/"
									+ summary.substring(1);
							FileInputStream sumFIS = new FileInputStream(
									sumFile);
							Properties conf = new Properties();// charset
																// iso-8859-1
							conf.load(sumFIS);
							sumFIS.close();
							String propName = (tpdu.size() + 2) + "-"
									+ si.getOS_EventId();
							si.setSummary(setSummary(
									new String(conf.getProperty(propName)
											.trim().getBytes("ISO-8859-1"),
											"GBK"), trapContent));
						}
						continue;
					}

					// get node from trap PDU
					if (line.indexOf("@Node = ") >= 0) {
						String para = line.split("\\=")[1].trim();
						if (para.startsWith("$")) {
							si.setNode(trapContent[Integer.parseInt(para
									.substring(1)) - 1]);
						} else if (para.equals("Undefined"))
							si.setNode(ti.getSender().toString().split("/")[0]);
						continue;
					}

					// get timestamp
					if (line.indexOf("@Timestamp = ") >= 0) {
						String para = line.split("\\=")[1].trim();
						if (para.equals("Undefined"))
							continue;
					}

					// get objectname
					if (line.indexOf("@Objectname = ") >= 0) {
						if (line.split("\\=")[1].trim().equals("Undefined")) {
							if (si.getNode() != null)
								si.setObjectname(si.getNode());
						} else if (line.split("\\=")[1].trim().indexOf("$") == 0) {
							si.setObjectname(trapContent[Integer.parseInt(line
									.split("\\=")[1].trim().substring(1)) - 1]);
						}
					}

					// get objecttype
					if (line.indexOf("@Objecttype = ") >= 0) {
						if (line.split("\\=")[1].trim().equals("Undefined"))
							si.setObjecttype("snmp trap");
						else {
							String objType = line.split("\\=")[1].trim();
							if (objType.indexOf("$") >= 0)
								si.setObjecttype(trapContent[Integer
										.parseInt(objType.substring(1)) - 1]);
							else
								si.setObjecttype(objType);
						}
					}

					if (line.indexOf("$OS_Severity = ") >= 0) {
						OS_Severity = line.split("\\=")[1].trim();
						continue;
					}
					if (line.indexOf("$DEFAULT_Severity = ") >= 0) {
						DEFAULT_Severity = line.split("\\=")[1].trim();
						continue;
					}
					if (line.indexOf("$DEFAULT_Type = ") >= 0) {
						DEFAULT_Type = line.split("\\=")[1].trim();
						continue;
					}
					if (line.indexOf("$DEFAULT_ExpireTime = ") >= 0) {
						DEFAULT_ExpireTime = line.split("\\=")[1].trim();
						continue;
					}
					if (line.indexOf("@Identifier = ") >= 0) {
						// use trap sender ip as node
						if (si.getNode() == null)
							si.setNode(ti.getSender().toString().split("/")[0]);
						String identifier = line.split("\\=")[1].trim();
						// get identifier
						si.setIdentifier(setIdentifier(identifier, trapContent));
						continue;
					}
				}
				break;
			}
		}
		br.close();
		fis.close();

		if (si.getObjectname() == null)
			si.setObjectname(si.getNode());
		// check if something went wrong
		if (si.getIdentifier() == null || si.getSummary() == null)
			return false;
		return true;
	}

	/* use oid to locate rule file */
	private FileInputStream ruleLocator() throws IOException {
		// read config file, get coresponding rule file name
		String prop = "";
		Properties conf = new Properties();
		FileInputStream fis = new FileInputStream(mibCfgFile);
		conf.load(fis);
		fis.close();
		prop = conf.getProperty(oid).trim();
		if (prop == null || prop.equals(""))// no file found for this oid
			return null;
		else {
			String mibFile = ruleDir + "/" + prop + ".rules";
			FileInputStream mibFIS = new FileInputStream(mibFile);
			return mibFIS;
		}
	}

	public void HandleTrap() {
		try {
			dbConn();
			if (ti.getVersion() == PDU.TRAP) {
				if (formatTrap()) {
					int ret = db.insertTrap(si, location);
					if (ret == 0)
						logger.info("1 new alarm added to db.");
					else if (ret == 1)
						logger.info("update 1 alarm.");
					else if (ret == 2)
						logger.info("clear 1 alarm.");
					else {
						logger.error("!!!!!!!!!!!!!!!!!!!!");
						logger.error("add 1 trap alarm and db return error. return code="
								+ ret);
						logger.info(si.getOS_EventId());
						logger.info("identifier of this trap: "
								+ si.getIdentifier());
						logger.info("alarm level of this trap: "
								+ si.getSeverity());
						logger.info("summary of this trap: "
								+ new String(si.getSummary().getBytes("GBK"),
										System.getProperty("file.encoding")));
						logger.info("alertKey of this trap: "
								+ si.getAlertKey());
						logger.error("!!!!!!!!!!!!!!!!!!!!");
					}
				} else {
					logger.info("--------------------");
					logger.info("no rules suitable for this trap.");
					logger.info(ti.getTrapPDU());
					logger.info("--------------------");
				}
			}
			db.destroy();
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("!!!!!!!!!!!!!!!!!!!!");
			logger.error("error occured when trying to add 1 trap alarm.");
			logger.error(e);
			logger.info("--------info of this trap--------");
			logger.info(si.getOS_EventId());
			logger.info("identifier of this trap: " + si.getIdentifier());
			logger.info("alarm level of this trap: " + si.getSeverity());
			logger.info("summary of this trap: " + si.getSummary());
			logger.info("alertKey of this trap: " + si.getAlertKey());
			logger.error("!!!!!!!!!!!!!!!!!!!!");
		} finally {
			db.destroy();
		}
	}

	/* parse summary */
	private String setSummary(String summary, String[] content) {
		try {
			// String sum = "";
			StringBuilder sum = new StringBuilder("");
			// String[] tmp = summary.split("\\:");
			// sum += tmp[0] + ":";
			// for (int i = 1; i < tmp.length; i++) {
			// String[] tmp2 = tmp[i].split("\\,");
			// int position = Integer.parseInt(tmp2[0].substring(1)) - 1;
			// sum += content[position] + " " + tmp2[1] + ":";
			// }
			// sum = sum.substring(0, sum.length() - 1);

			String[] tmp = summary.split("\\,");
			for (int i = 0; i < tmp.length; i++) {
				if (tmp[i].indexOf(":") >= 0) {
					String[] tmp2 = tmp[i].split("\\:");
					int position = Integer.parseInt(tmp2[1].substring(1)) - 1;
					sum.append(tmp2[0]).append(": ").append(content[position])
							.append(", ");
					// sum += tmp2[0] + ": " + content[position] + ", ";
				} else
					sum.append(tmp[i]).append(", ");
				// sum += tmp[i] + ", ";
			}

			// windows system, convert charset
			if (!System.getProperty("os.name").equals("Linux")) {
				String encoding = System.getProperty("file.encoding");
				String sumUTF8 = new String(sum.substring(0, sum.length() - 2)
						.getBytes(encoding), "GBK");
				return sumUTF8;
			} else
				// charset will be set in shell script
				return sum.substring(0, sum.length() - 2);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e);
			return null;
		}
	}

	/* identifier is used to identifier distinct trap alarm */
	private String setIdentifier(String identifier, String[] content) {
		try {
			// String id = "";
			StringBuilder id = new StringBuilder("");
			String[] tmp = identifier.split("\\+");
			for (int i = 0; i < tmp.length; i++) {
				if (tmp[i].trim().indexOf("@") == 0) {
					String method = "get" + tmp[i].trim().substring(1);
					id.append(
							si.getClass().getMethod(method, new Class[] {})
									.invoke(si, new Object[] {})).append(",");
					// id += si.getClass().getMethod(method, new Class[] {})
					// .invoke(si, new Object[] {});
					// id += ",";
				} else if (tmp[i].trim().indexOf("$") == 0) {
					int j = Integer.parseInt(tmp[i].trim().substring(1));
					id.append(content[j - 1]).append(",");
					// id += content[j - 1];
					// id += ",";
				} else if (tmp[i].trim().equals(" ")) {
					continue;
				} else {
					id.append(tmp[i].trim()).append(",");
					// id += tmp[i].trim();
					// id += ",";
				}
			}
			return id.substring(0, id.length() - 1);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e);
			return null;
		}
	}

	/*
	 * @format1: @severity = @position,$value:level,$value:level....
	 * 
	 * @format2: @severity = @0 --clear alarm
	 * 
	 * @format3: @severity = @1000,level --self defined level
	 */
	private void setSeverity(String severityIn, String[] content) {
		try {
			String[] severity = severityIn.split("\\=");
			if (severity[0].trim().equals("@severity")) {
				String[] value = severity[1].split("\\,");
				// severity position in trap
				int i = Integer.parseInt(value[0].trim().substring(1)) - 1;
				if (i == -1) {// clear alarm
					si.setSeverity(0);
				} else if (i == 999) {// no severity in trap, self defined
					si.setSeverity(Integer.parseInt(value[1]));
				} else {
					// get alarm value from the trap PDU
					String tmp = content[i];
					for (int j = 1; j < value.length; j++) {
						if (value[j].indexOf("$" + tmp) == 0) {
							int alarmlev = Integer.parseInt(value[j]
									.split("\\:")[1]);
							si.setSeverity(alarmlev);
							break;
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e);
			si.setSeverity(-1);
		}
	}

	/* create and compile dynamic class which works as script */
	private void buildDynamicClass(String script) throws Exception {
		// get method name which begin with '__'
		String dynamicMethod = script.split("\\|\\|")[0].trim().substring(2);
		// get method parameters number
		int paraNum = script.split("\\|\\|").length - 1;

		// locate java source file
		File file = new File("./com/sanss/trapParse/method/Temp_"
				+ dynamicMethod + "_" + paraNum + ".java");
		if (!file.exists()) {
			file.createNewFile();
			// build temp class
			StringBuilder javaSource = new StringBuilder();
			javaSource.append("package com.sanss.trapParse.method;\n")
					.append("import com.sanss.trapParse.method.*;\n")
					.append("public class Temp_").append(dynamicMethod)
					.append("_").append(paraNum)
					.append(" extends ScriptContext{\n")
					.append("\t@Override\n")
					.append("\tpublic void execute() {\n")
					.append("\t\tcontext[").append(paraNum + 1).append("] = ")
					.append("Method.__").append(dynamicMethod)
					.append("((String[])context[0],");
			for (int i = 1; i < paraNum + 1; i++)
				javaSource.append("(String)").append("context[").append(i)
						.append("],");
			javaSource.deleteCharAt(javaSource.length() - 1);
			javaSource.append(");\n\t}\n}\n");
			// write file
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(javaSource.toString().getBytes());
			fos.flush();
			fos.close();
			// compile java source file
			String[] compileArgs = new String[] { file.getAbsolutePath() };
			com.sun.tools.javac.Main.compile(compileArgs);
		}
	}

	/* invoke dynamic class to call method */
	private String invokeMethod(String methodScript) throws Exception {
		// get script command and args
		String[] args = methodScript.split("\\|\\|");

		// calling dynamic method
		buildDynamicClass(methodScript);
		URLClassLoader loader = new URLClassLoader(new URL[] { new File(".")
				.toURI().toURL() });
		Class<?> scriptClass = loader
				.loadClass("com.sanss.trapParse.method.Temp_"
						+ args[0].trim().substring(2) + "_" + (args.length - 1));
		ScriptContext obj = (ScriptContext) scriptClass.newInstance();

		// arg.length + 1: +1 means String[] trapContent
		obj.context = new Object[args.length + 1];
		// by default, set context[0] to trapContent
		obj.context[0] = trapContent;

		for (int i = 1; i < args.length; i++)
			obj.context[i] = args[i];
		obj.execute();
		return (String) obj.context[args.length];
	}

	/* dynamic call setter method to set string value */
	private void dynamicStringValueSetter(String setterName, String setterValue)
			throws Exception {
		// set method result to specific statusInfo para
		String setter = "set" + setterName;
		si.getClass().getMethod(setter, new Class[] { String.class })
				.invoke(si, new Object[] { new String(setterValue) });
	}

	private String getChinese(String octetString) {
		try {
			String[] temps = octetString.split(":");
			byte[] bs = new byte[temps.length];
			for (int i = 0; i < temps.length; i++)
				bs[i] = (byte) Integer.parseInt(temps[i], 16);

			return new String(bs, "GB2312");
		} catch (Exception e) {
			return null;
		}
	}

}
